/* **********************************************************************************
*
* Copyright (c) Microsoft Corporation. All rights reserved.
*
* This source code is subject to terms and conditions of the Shared Source License
* for DSL Editor PowerToy. A copy of the license can be found in the License.htm file
* at the root of this distribution. If you can not locate the Shared Source License
* for DSL Editor PowerToy, please obtain a copy from: http://www.codeplex.com/dsltreegrideditor/Project/License.aspx.
* By using this source code in any fashion, you are agreeing to be bound by
* the terms of the Shared Source License for DSL Editor PowerToy.
*
* You must not remove this notice, or any other, from this software.
*
* **********************************************************************************/
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Data;
using System.Diagnostics;
using System.Drawing;
using System.Text;
using System.Windows.Forms;

namespace DslEditorPowerToy.Controls
{
    /// <summary>
    /// Control to manage multiple leafs each containing a single editor
    /// </summary>
    public partial class LeafedHostedEditorControl : UserControl, IMonitorSelection, ISelectionService
    {
        public const int EditorImageWidth = 16;
        public const int EditorImageHeight = 16;
        private const int ControlMarginX = 1;
        private const int ControlMarginY = 2;
        private const int ButtonInnerPaddingX = 3;
        private const int ButtonInnerPaddingY = 2;

        private Dictionary<Guid, HostedEditor> _editors = null;
        private HostedEditor _currentEditor = null;
        private NavigationOrientationStyle _navigationStyle = NavigationOrientationStyle.Vertical;

        #region Constructors
        public LeafedHostedEditorControl()
        {
            this._editors = new Dictionary<Guid, HostedEditor>();

            InitializeComponent();

            //Initialise toolbar and strip
            ProfessionalColorTable professionalColorTable = new ProfessionalColorTable();
            professionalColorTable.UseSystemColors = true;
            this.toolbar.Renderer = new ToolStripProfessionalRenderer(professionalColorTable);
            this.toolStripComboBox.Margin = new Padding(ControlMarginX, ControlMarginY, ControlMarginX, ControlMarginY);
    
            InitialiseControls();
        }
        #endregion

        #region IMonitorSelection Members
        public virtual void NotifySelectionChanged(ISelectionProvider selectionProvider, SelectionChangeSource source)
        {
            //Propagate selection changes to all editors
            foreach (KeyValuePair<Guid, HostedEditor> editor in this._editors)
            {
                if (editor.Value.Control is IMonitorSelection)
                {
                    IMonitorSelection control = editor.Value.Control as IMonitorSelection;
                    control.NotifySelectionChanged(selectionProvider, source);
                }
            }
        }

        #endregion

        #region ISelectionService Members

        public bool GetComponentSelected(object component)
        {
            if ((null != this._currentEditor)
                && (this._currentEditor.Control is ISelectionService))
                return (this._currentEditor.Control as ISelectionService).GetComponentSelected(component);

            return false;
        }

        public ICollection GetSelectedComponents()
        {
            if ((null != this._currentEditor)
                && (this._currentEditor.Control is ISelectionService))
                return (this._currentEditor.Control as ISelectionService).GetSelectedComponents();

            return null;
        }

        public object PrimarySelection
        {
            get
            {
                if ((null != this._currentEditor)
                    && (this._currentEditor.Control is ISelectionService))
                    return (this._currentEditor.Control as ISelectionService).PrimarySelection;

                return null;
            }
        }

        public event EventHandler SelectionChanged;

        public event EventHandler SelectionChanging;

        public int SelectionCount
        {
            get
            {
                if ((null != this._currentEditor)
                    && (this._currentEditor.Control is ISelectionService))
                    return (this._currentEditor.Control as ISelectionService).SelectionCount;

                return 0;
            }
        }

        public void SetSelectedComponents(ICollection components, SelectionTypes selectionType)
        {
            if ((null != this._currentEditor)
                && (this._currentEditor.Control is ISelectionService))
                (this._currentEditor.Control as ISelectionService).SetSelectedComponents(components, selectionType);
        }

        public void SetSelectedComponents(ICollection components)
        {
            if ((null != this._currentEditor)
                && (this._currentEditor.Control is ISelectionService))
                (this._currentEditor.Control as ISelectionService).SetSelectedComponents(components);
        }

        #endregion

        #region Protected Properties
        /// <summary>
        /// Gets whether toolbar is showing
        /// </summary>
        protected bool ShowingToolBar
        {
            get
            {
                return (this._editors.Count > 1);
            }
        }
        #endregion

        #region Public Properties
        /// <summary>
        /// Gets the control navigation style
        /// </summary>
        public NavigationOrientationStyle NavigationStyle
        {
            get
            {
                return this._navigationStyle;
            }
            set
            {
                this._navigationStyle = value;
                SetToolBarOrientation();
            }
        }
        /// <summary>
        /// Gets the currently selected editor
        /// </summary>
        public HostedEditor CurrentEditor
        {
            get
            {
                return this._currentEditor;
            }
        }
        #endregion

        #region Public Events
        public event EventHandler EditorChanged;
        #endregion

        #region Public Methods
        /// <summary>
        /// Adds an editor to the control
        /// </summary>
        /// <param name="editor"></param>
        /// <param name="editorId"></param>
        public void AddEditor(HostedEditor editor)
        {
            if (null == editor)
                throw new ArgumentNullException(Resources.EditorNullException);
            if (null == editor.Control)
                throw new ArgumentNullException(Resources.EditorControlNullException);

            //Adds editor to control
            Debug.Assert((false == this._editors.ContainsKey(editor.Id)), "Duplicate editor identifiers");
            if (true == this._editors.ContainsKey(editor.Id))
                throw new ArgumentOutOfRangeException(Resources.DuplicateEditorException);
            this._editors.Add(editor.Id, editor);

            //Ensure Icon
            Bitmap icon = EnsureIcon(editor.Image, this._editors.Count);

            //Add ToolStrip button
            ToolStripButton item = new ToolStripButton(editor.Caption, icon, toolbarItem_Click);
            item.CheckOnClick = true;
            item.DisplayStyle = ToolStripItemDisplayStyle.None;
            item.ImageTransparentColor = Color.FromKnownColor(KnownColor.Magenta);
            item.Tag = editor;
            item.Margin = new Padding(ControlMarginX, ControlMarginY, ControlMarginX, ControlMarginY);
            item.Padding = new Padding(ButtonInnerPaddingX, ButtonInnerPaddingY, ButtonInnerPaddingX, ButtonInnerPaddingY);
            item.Visible = false;
            this.toolbar.Items.Add(item);

            //Add Item to toolbar dropdown
            this.toolStripComboBox.Items.Add(editor);

            //Add Control to host
            this.hostPanel.Controls.Add(editor.Control);

            //Set control appearance
            editor.Control.Dock = DockStyle.Fill;
            editor.Control.Hide();

            //Reinitialise toolbar
            SetToolBarOrientation();
        }
        /// <summary>
        /// Activates (displays) the specified editor
        /// </summary>
        /// <param name="editorId"></param>
        public void ActivateEditor(Guid editorId)
        {
            Debug.Assert((true == this._editors.ContainsKey(editorId)), "Editor with specified identifier not found");
            if (false == this._editors.ContainsKey(editorId))
                throw new ArgumentOutOfRangeException(Resources.EditorNotFoundException);

            //Get the new editor
            HostedEditor newEditor = this._editors[editorId] as HostedEditor;

            //Check not current editor
            if (this._currentEditor != newEditor)
            {
                //Unregister old editor
                if (null != this._currentEditor)
                {
                    if (this._currentEditor.Control is ISelectionService)
                    {
                        ((ISelectionService)this._currentEditor.Control).SelectionChanged -= new EventHandler(_editor_SelectionChanged);
                        ((ISelectionService)this._currentEditor.Control).SelectionChanging -= new EventHandler(_editor_SelectionChanging);
                    }
                }
                //Register new editor
                if (newEditor.Control is ISelectionService)
                {
                    ((ISelectionService)newEditor.Control).SelectionChanged += new EventHandler(_editor_SelectionChanged);
                    ((ISelectionService)newEditor.Control).SelectionChanging += new EventHandler(_editor_SelectionChanging);
                }

                //Save current editor
                this._currentEditor = newEditor;

                //Shows editor controls
                this.SetActiveEditorControls();

                //Raise change of editor
                OnEditorChanged();

                //Raise change of selection in new editor
                OnSelectionChanged();
            }
        }
        #endregion

        #region Protected Methods
        /// <summary>
        /// Raises editor changed event
        /// </summary>
        protected void OnEditorChanged()
        {
            if (null != this.EditorChanged)
                this.EditorChanged(this, new EventArgs());
        }
        /// <summary>
        /// Raises the selection changed event from specified control
        /// </summary>
        /// <param name="sender"></param>
        protected void OnSelectionChanged()
        {
            if (null != this.SelectionChanged)
                this.SelectionChanged(this, new EventArgs());
        }
        /// <summary>
        /// Raises the selection changing event from specified control
        /// </summary>
        /// <param name="sender"></param>
        protected void OnSelectionChanging()
        {
            if (null != this.SelectionChanging)
                this.SelectionChanging(this, new EventArgs());
        }
        /// <summary>
        /// Passes focus to panel
        /// </summary>
        /// <param name="e"></param>
        protected override void OnGotFocus(EventArgs e)
        {
            //Pass focus to active control
            if (true == this.hostPanel.Visible)
                this.hostPanel.Focus();
            else
            {
                if (true == this.emptyLabel.Visible)
                    this.emptyLabel.Focus();
            }

            base.OnGotFocus(e);
        }
        #endregion

        #region Control Handlers
        /// <summary>
        /// Handles button click on toolbar
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void toolbarItem_Click(object sender, EventArgs e)
        {
            //Get clicked item
            if (sender is ToolStripButton)
            {
                ToolStripButton button = sender as ToolStripButton;
                if (button.Tag is HostedEditor)
                    ActivateEditor(((HostedEditor)button.Tag).Id);
            }
        }
        /// <summary>
        /// Handles item change in dropdown
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void toolStripComboBox_SelectedIndexChanged(object sender, EventArgs e)
        {
            //Get selected item
            object selectedItem = this.toolStripComboBox.SelectedItem;
            if ((null != selectedItem)
                && (selectedItem is HostedEditor))
                ActivateEditor(((HostedEditor)selectedItem).Id);
        }
        /// <summary>
        /// Handles selection changed event from current editor control
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void _editor_SelectionChanged(object sender, EventArgs e)
        {
            OnSelectionChanged();
        }
        /// <summary>
        /// Handles selection changing event from current editor control
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void _editor_SelectionChanging(object sender, EventArgs e)
        {
            OnSelectionChanging();
        }
        #endregion

        #region Private Methods
        /// <summary>
        /// Initialises controls to starting state
        /// </summary>
        private void InitialiseControls()
        {
            //Show Label
            this.emptyLabel.Show();
            
            //Hide Panel
            this.hostPanel.Hide();

            //Orientate the toolbar
            SetToolBarOrientation();
        }
        /// <summary>
        /// Changes orientation of toolbar and initialises its controls
        /// </summary>
        private void SetToolBarOrientation()
        {
            //Set naviagtion style controls
            DockStyle dockStyle = DockStyle.None;
            ToolStripLayoutStyle layoutStyle = ToolStripLayoutStyle.HorizontalStackWithOverflow;
            ToolStripItemDisplayStyle displayStyle = ToolStripItemDisplayStyle.None;
            bool showButtons = false;
            switch (this._navigationStyle)
            {
                case NavigationOrientationStyle.HorizontalList:
                    dockStyle = DockStyle.Top;
                    layoutStyle = ToolStripLayoutStyle.HorizontalStackWithOverflow;
                    displayStyle = ToolStripItemDisplayStyle.None;
                    showButtons = false;
                    break;
                case NavigationOrientationStyle.HorizontalTabsTop:
                    dockStyle = DockStyle.Top;
                    layoutStyle = ToolStripLayoutStyle.HorizontalStackWithOverflow;
                    displayStyle = ToolStripItemDisplayStyle.ImageAndText;
                    showButtons = true;
                    break;
                case NavigationOrientationStyle.HorizontalTabsBottom:
                    dockStyle = DockStyle.Bottom;
                    layoutStyle = ToolStripLayoutStyle.HorizontalStackWithOverflow;
                    displayStyle = ToolStripItemDisplayStyle.ImageAndText;
                    showButtons = true;
                    break;
                case NavigationOrientationStyle.Vertical:
                    dockStyle = DockStyle.Left;
                    layoutStyle = ToolStripLayoutStyle.VerticalStackWithOverflow;
                    displayStyle = ToolStripItemDisplayStyle.Image;
                    showButtons = true;
                    break;
            }
            this.toolbar.Dock = dockStyle;
            this.toolbar.LayoutStyle = layoutStyle;
            foreach (ToolStripButton button in GetEditorButtons())
            {
                button.DisplayStyle = displayStyle;
                button.Visible = showButtons;
            }

            //Hide/show dropdown list
            this.toolStripComboBox.Visible = (NavigationOrientationStyle.HorizontalList == this._navigationStyle);
            this.toolStripLabel.Visible = (NavigationOrientationStyle.HorizontalList == this._navigationStyle);

            //Show/Hide ToolBar
            if (true == this.ShowingToolBar)
            {
                this.toolbar.Show();

                //Resize Panel (we have to do this after Show() otherwise calculated width and height of toolbar are wrong)
                this.hostPanel.Padding = new Padding(0, 0, 0, 0);
            }
            else
            {
                //Restore Panel size
                this.hostPanel.Padding = new Padding(0, 0, 0, 0);

                this.toolbar.Hide();
            }
        }
        /// <summary>
        /// Sets controls for active editor
        /// </summary>
        /// <param name="editor"></param>
        private void SetActiveEditorControls()
        {
            //Show Panel
            this.hostPanel.Show();

            //Hide Label
            this.emptyLabel.Hide();
            
            //Hide all editors
            foreach (KeyValuePair<Guid, HostedEditor> editor in this._editors)
                editor.Value.Control.Hide();            

            //Show current editor
            if (null != this._currentEditor)
                this._currentEditor.Control.Show();            
            
            //Update UI Cues
            this.SyncEditorControls();
        }
        /// <summary>
        /// Syncs UI state of all controls to reflect selected editor
        /// </summary>
        private void SyncEditorControls()
        {
            //Select the dropdown item
            this.toolStripComboBox.SelectedItem = this._currentEditor;

            //Select current editor button
            foreach (ToolStripButton button in GetEditorButtons())
            {
                button.Checked = false;
                if (button.Tag as HostedEditor == this._currentEditor)
                    button.Checked = true;
            }
        }
        /// <summary>
        /// Ensures icon is correct dimensions and valid, or creates a stock one
        /// </summary>
        /// <param name="bitmap"></param>
        /// <returns></returns>
        private Bitmap EnsureIcon(Bitmap bitmap, int index)
        {
            try
            {
                Rectangle boundingRect = new Rectangle(0, 0, EditorImageWidth, EditorImageHeight);
                RectangleF boundingRectF = new RectangleF(0F, 0F, boundingRect.Width, boundingRect.Height);
                using (Font font = new Font(FontFamily.GenericSansSerif, 10F, FontStyle.Bold))
                {
                    Brush textBrush = SystemBrushes.WindowText;
                    Pen linePen = SystemPens.WindowText;

                    //Check if valid
                    if (null == bitmap)
                    {
                        //Create a bitmap (for current index)
                        Bitmap newBitmap = new Bitmap(boundingRect.Width, boundingRect.Height);
                        using (Graphics graphics = Graphics.FromImage(newBitmap))
                        {
                            //Write number in popular font
                            graphics.DrawString(index.ToString(), font, textBrush, boundingRectF);
                            graphics.DrawRectangle(linePen, 0, 0, (boundingRect.Width - 1), (boundingRect.Height - 1));

                            return newBitmap;
                        }
                    }
                    else
                    {
                        //Ensure bitmap is correct dimensions
                        using (Graphics graphics = Graphics.FromImage(bitmap))
                        {
                            if ((bitmap.Height > EditorImageHeight)
                                || (bitmap.Width > EditorImageWidth))
                            {
                                //Resize image
                                graphics.DrawImage(bitmap, boundingRect);
                            }

                            return bitmap;
                        }
                    }
                }
            }
            catch (Exception)
            {
                return bitmap;
            }
        }
        /// <summary>
        /// Retrieves list of editor buttons
        /// </summary>
        /// <returns></returns>
        private List<ToolStripButton> GetEditorButtons()
        {
            List<ToolStripButton> buttons = new List<ToolStripButton>();

            //Check the correct toolbar button
            foreach (ToolStripItem item in this.toolbar.Items)
            {
                //Ensure button
                if (item is ToolStripButton)
                {
                    //Ensure a button assigned to editor
                    if (item.Tag is HostedEditor)
                        buttons.Add(item as ToolStripButton);
                }
            }

            return buttons;
        }
        #endregion
    }
}
